package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.constants.PromotionConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.vo.UserCouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.CodeUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author itheima
 * @since 2023-07-02
 */
@Service
//故意没有加事务
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {


    @Autowired
    private CouponMapper couponMapper;


    @Lazy
    @Autowired
    private UserCouponServiceImpl userCouponService;

    @Autowired
    private RedissonClient redissonClient;



    //领取优惠券
//    @Override
//    public void receiveCoupon(Long couponId) {
//        // 1-查询优惠券信息
//        Coupon coupon = couponMapper.selectById(couponId);
//        if(Objects.isNull(coupon)){
//            throw new BadRequestException("优惠券信息不存在");
//        }
//        //2.判断是否正在发放
//        LocalDateTime now = LocalDateTime.now();
//        if(now.isBefore(coupon.getIssueEndTime()) || now.isAfter(coupon.getIssueEndTime())
//        || coupon.getStatus() != CouponStatus.ISSUING){
//            throw new BizIllegalException("优惠券暂不处于可领取窗口!");
//        }
//        //3-判断库存是否充足
//        if(coupon.getTotalNum()<=coupon.getIssueNum()){
//            throw new BadRequestException("优惠券库存不足!");
//        }
//        //4-判断是否超出限领数量
////        synchronized (UserContext.getUser().toString().intern()){
////            userCouponService.checkAndSave(couponId,coupon);
////        }
//
//        //获取锁对象，指定锁名称
//        String key="lock:coupon:uid"+UserContext.getUser();
//        RLock lock = redissonClient.getLock(key);
//
//        try {
//            //尝试获取锁对象，参数: 锁定时间，时间单位
//            boolean falg = lock.tryLock(5, TimeUnit.SECONDS);
//            if(!falg){
//                throw new BizIllegalException("获取分布式锁失败!");
//            }
//            //执行业务逻辑
//            //IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
//            userCouponService.checkAndSave(couponId, coupon);
//        } catch (InterruptedException e) {
//            throw new BizIllegalException("处理业务失败!");
//        }finally {
//            //释放锁
//            lock.unlock();
//        }
//    }

    @Transactional
    public void checkAndSave(Long couponId, Coupon coupon) {
        Integer count = this.lambdaQuery()
                .eq(UserCoupon::getCouponId, couponId)
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .count();
        if(count >= coupon.getUserLimit()){
            throw new BizIllegalException("你已经超领了，请勿重复领取");
        }
        //5- 更新优惠券的已领数量
        int i=couponMapper.incrementIssueNum(couponId);
        if(i==0){
            throw new BadRequestException("优惠券库存不足");
        }
        //6- 保存用户优惠券信息
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(UserContext.getUser());
        userCoupon.setCouponId(couponId);

        //计算优惠券有效期开始时间和结束时间
        LocalDateTime begin = coupon.getTermBeginTime();
        LocalDateTime end = coupon.getTermEndTime();
        //如果穿入了有效天数，结束时间就是当前时间+天数
        if(Objects.isNull(coupon.getTermBeginTime())){
            begin=LocalDateTime.now();
            end=LocalDateTime.now().plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(begin);
        userCoupon.setTermEndTime(end);

        this.save(userCoupon);
    }

    @Autowired
    private IExchangeCodeService codeService;

    private static final String COUPON_CODE_MAP_KEY="coupon:code:map";

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitMqHelper rabbitMqHelper;
    //领取优惠券
    @Override
    public void receiveCoupon(Long couponId) {
        // 1-查询优惠券信息
        String couponKey = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(couponKey);
        if(objMap.isEmpty()){
            throw new BadRequestException("优惠券信息不存在");
        }
        Coupon coupon = BeanUtils.mapToBean(objMap, Coupon.class, CopyOptions.create());
        //2- 判断库存是否重组(缓存数据没有issnum，因此直接用剩余量判断，每次发放完总数-1)
        if(coupon.getTotalNum()<=0){
            throw new BadRequestException("优惠券库存不足");
        }
        // 2.校验每人限领数量
        // 2-1 查询领取数量(获取的同时立马加1，实现用户券+1的效果)
        String userCouponKey = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
        Long count = redisTemplate.opsForHash().increment(userCouponKey, UserContext.getUser().toString(), 1);
        // 2-2 校验限领数量
        if(count>coupon.getUserLimit()){
            redisTemplate.opsForHash().increment(userCouponKey,UserContext.getUser().toString(),-1);
            throw new BadRequestException("超出领取数量");
        }
        //4.扣减优惠券库存(判断完立马-1，实现总量-1的效果)
        redisTemplate.opsForHash().increment(
                PromotionConstants.COUPON_CACHE_KEY_PREFIX+couponId,"totalNum",-1
        );

        //5. 发送MQ消息
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(UserContext.getUser());
        uc.setCouponId(couponId);
        rabbitMqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE,MqConstants.Key.COUPON_RECEIVE_KEY,uc);


    }

    //兑换码兑换优惠券
    @Override
    public void exchangeCoupon(String code) {
        // 1-解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        //2- 校验是否兑换过(兑换码的交给兑换码服务处理，直接给true即设置为1)
        Boolean exchanged = redisTemplate.opsForValue().setBit(COUPON_CODE_MAP_KEY, serialNum, true);
        if(exchanged){
            throw new BizIllegalException("兑换码已经被兑换过");
        }
        //3-查询兑换码数据并判断是否存在
        ExchangeCode exchangeCode = codeService.getById(serialNum);
        if(Objects.isNull(exchangeCode)){
            throw new BadRequestException("兑换码不存在");
        }
        //4-判断是否过期
        LocalDateTime now = LocalDateTime.now();
        if(now.isAfter(exchangeCode.getExpiredTime())){
            throw new BizIllegalException("兑换码已过期");
        }
        try {
            //5- 校验并生成用户券
            //5-1查询优惠券信息
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            Long userId = UserContext.getUser();

            //判断是否超出限领数量
            Integer count = this.lambdaQuery()
                    .eq(UserCoupon::getCouponId, coupon.getId())
                    .eq(UserCoupon::getUserId, UserContext.getUser())
                    .count();
            if(count>=coupon.getUserLimit()){
                throw new BizIllegalException("你已经超领了,请勿重复领取");
            }
            //5.2-更新优惠券的已经发放的数量+1
            int i=couponMapper.incrementIssueNum(coupon.getId());
            if(i==0){
                throw new BadRequestException("优惠券库存不足");
            }
            //5.3-新增一个用户券
            UserCoupon uc = new UserCoupon();
            uc.setUserId(userId);
            uc.setCouponId(coupon.getId());
            //2. 有效期信息
            LocalDateTime termBeginTime = coupon.getTermBeginTime();
            LocalDateTime termEndTime = coupon.getTermEndTime();
            if(termBeginTime==null){
                termBeginTime = LocalDateTime.now();
                termEndTime=termBeginTime.plusDays(coupon.getTermDays());
            }
            uc.setTermBeginTime(termBeginTime);
            uc.setTermEndTime(termEndTime);
            //3. 保存
            this.save(uc);
            //5-4 更新兑换码状态
            codeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId,userId)
                    //可以换成枚举值
            .set(ExchangeCode::getStatus,2)
                    .eq(ExchangeCode::getId,serialNum)
                    .update();
        }catch (Exception e){
            log.error(e.getMessage());
            //异常时，需要重置兑换标记为0
            redisTemplate.opsForValue().setBit(COUPON_CODE_MAP_KEY,serialNum,false);
            throw new BizIllegalException("兑换码业务失败!");
        }

    }

    //分页查询我的优惠券
    @Override
    public PageDTO<UserCouponVO> queryUser(UserCouponQuery query) {
        // 1. 查询数据库
        Page<UserCoupon> page = lambdaQuery()
                .eq(query.getStatus() != null, UserCoupon::getStatus, query.getStatus())
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .page(query.toMpPage("term_end_time", true));

        //2. 非空处理
        List<UserCoupon> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //3. 查询优惠券规则
        Set<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);
        if(coupons == null || coupons.size() != couponIds.size()){
            //优惠券信息找不到
            throw new BizIllegalException("优惠券数据异常");
        }
        Map<Long, Coupon> couponMap = coupons.stream().collect(Collectors.toMap(Coupon::getId, c -> c));
        //4. 转VO
        return PageDTO.of(page,userCoupon -> {
            Coupon coupon = couponMap.get(userCoupon.getCouponId());
            UserCouponVO v = new UserCouponVO();
            v.setName(coupon.getName());
            v.setDiscountType(coupon.getDiscountType());
            v.setSpecify(coupon.getSpecific());
            v.setThresholdAmount(coupon.getThresholdAmount());
            v.setMaxDiscountAmount(coupon.getMaxDiscountAmount());
            v.setId(userCoupon.getId());
            v.setTermEndTime(userCoupon.getTermEndTime());
            return v;
        });
    }

    @Override
    public void checkAndCreateUserCoupon(UserCouponDTO uc) {
        //1. 查询优惠券
        Coupon coupon = couponMapper.selectById(uc.getCouponId());
        if(coupon==null){
            throw new BizIllegalException("优惠券不存在");
        }
        //2. 更新优惠券的已经发放的数量+1
        int r = couponMapper.incrementIssueNum(coupon.getId());
        if(r==0){
            throw new BizIllegalException("优惠券库存不足");
        }
        //新增一个优惠券
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(uc.getUserId());
        userCoupon.setCouponId(uc.getCouponId());
        //计算优惠券有效期开始和结束时间
        LocalDateTime begin = coupon.getTermBeginTime();
        LocalDateTime end = coupon.getTermEndTime();
        //如果传入了有效天数，结束时间就是当前时间+天数
        if(Objects.isNull(coupon.getTermBeginTime())){
            begin=LocalDateTime.now();
            end=LocalDateTime.now().plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(begin);
        userCoupon.setTermEndTime(end);
        this.save(userCoupon);
    }

    @Override
    @Transactional
    public void writeOffCoupon(List<Long> userCouponIds) {
        //1.查询优惠券
        List<UserCoupon> userCoupons = listByIds(userCouponIds);
        if(CollUtils.isEmpty(userCoupons)){
            return;
        }
        //2. 处理数据
        List<UserCoupon> list = userCoupons.stream()
                //过滤无效券
                .filter(coupon -> {
                    if (coupon == null) {
                        return false;
                    }
                    if (UserCouponStatus.UNUSED != coupon.getStatus()) {
                        return false;
                    }
                    LocalDateTime now = LocalDateTime.now();
                    return !now.isBefore(coupon.getTermBeginTime()) && !now.isAfter(coupon.getTermEndTime());
                })
                .map(coupon -> {
                    UserCoupon c = new UserCoupon();
                    c.setId(coupon.getId());
                    c.setStatus(UserCouponStatus.USED);
                    return c;
                })
                .collect(Collectors.toList());
        //4. 核销，修改优惠券状态
        boolean sucess = updateBatchById(list);
        if(!sucess){
            return;
        }
        //5. 更新已使用数量
        List<Long> couponIds = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        for (Long couponId : couponIds) {
            int c = couponMapper.incrementIssueNum(couponId);
            if(c<1){
                throw new DbException("更新优惠券使用数量失败!");
            }
        }

    }

    @Override
    @Transactional
    public void refundCoupon(List<Long> userCouponIds) {
        //1. 查询优惠券
        List<UserCoupon> userCoupons = listByIds(userCouponIds);
        if(CollUtils.isEmpty(userCoupons)){
            return;
        }
        //2. 处理优惠券数据
        List<UserCoupon> list = userCoupons.stream()
                //过滤无效券
                .filter(coupon -> coupon != null && UserCouponStatus.USED == coupon.getStatus())
                //更新状态字段
                .map(coupon -> {
                    UserCoupon c = new UserCoupon();
                    c.setId(coupon.getId());
                    //3. 判断有效期，是否已经过期，如果过期，则状态为 已过期，斗则状态为 未使用
                    LocalDateTime now = LocalDateTime.now();
                    UserCouponStatus status = now.isAfter(coupon.getTermEndTime()) ?
                            UserCouponStatus.EXPIRED : UserCouponStatus.UNUSED;
                    c.setStatus(status);
                    return c;
                }).collect(Collectors.toList());
        //4. 修改优惠券状态
        boolean sucess = updateBatchById(list);
        if(!sucess){
            return;
        }
        //5. 更新已使用数量
        List<Long> couponIds = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        int c = couponMapper.incrUsedNum(couponIds);
        if(c<1){
            throw new DbException("更新优惠券使用数量失败!");
        }

    }

    @Override
    public List<String> queryDiscountRules(List<Long> userCouponIds) {
        // 1.查询优惠券信息
        List<Coupon> coupons = baseMapper.queryCouponByUserCouponIds(userCouponIds, UserCouponStatus.USED);
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }
        // 2.转换规则
        return coupons.stream()
                .map(c -> DiscountStrategy.getDiscount(c.getDiscountType()).getRule(c))
                .collect(Collectors.toList());
    }
}
